package it.polimi.loganalyzer;

import it.polimi.loganalyzer.loader.Loader;
import it.polimi.loganalyzer.loader.UserTracker;
import it.polimi.loganalyzer.model.Model;
import it.polimi.loganalyzer.model.State;
import it.polimi.loganalyzer.model.mapper.DefaultMapper;
import it.polimi.loganalyzer.model.mapper.Mapper;
import it.polimi.loganalyzer.stemmer.Stemmer;

import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;

public class LogAnalyzer {

	private Loader loader;
	private Mapper mapper;
	private Stemmer stemmer;
	private UserTracker tracker;
	private Model model;
	private String logConfFile;
	private Logger log = Logger.getLogger(this.getClass().getSimpleName());
	private int lineCounter = 0;
	private boolean modelReady = false;

	public LogAnalyzer(String logConfFile) {
		this.logConfFile = logConfFile;
		this.mapper = new DefaultMapper();
		this.model = new Model("Init", "Goal", 0, 0);
		this.tracker = new UserTracker();
		PropertyConfigurator.configure(this.logConfFile);
	}

	public LogAnalyzer(String logConfFile, Mapper mapper) {
		this(logConfFile);
		if (mapper != null)
			this.mapper = mapper;
	}

	public void analyze(Loader loader) {
		if (loader == null) {
			throw new IllegalStateException(
					"Cannot set a null loader");
		}
		
		this.loader = loader;
		LogLine logLine = null;
		log.info("Fetching log lines");
		do {
			try {

				// Fetching a new LogLine from the log file.
				logLine = this.loader.next();
				this.lineCounter++;

				// If the fetched line is not null (EOF) and if is not a line to
				// be stemmed We process it.
				if (logLine != null && !stem(logLine)) {

					// Loading the States associated to the LogLine
					State destination = mapper.matches(logLine.getUrl());
					State source = getSourceState(logLine);

					if (destination == null) {
						log.info("Unmacthed state: " + logLine.getUrl());
						continue;
					}
					log.debug("Matched state: " + destination + " for url: "
							+ logLine.getUrl());
					// Signaling to the inference engine that the user,
					// identified by its ip address,
					// reached a certain state.
					// If a referrer source state is available we signal a
					// transition from a
					model.signalTransition(source, destination);
					tracker.updateCurrentState(logLine.getIp(), destination);
					// log.info(this.lineCounter + " lines parsed so far");
				}
			} catch (IllegalArgumentException e) {
				log.error(e.getMessage());
				e.printStackTrace();
				System.exit(-1);
			}
		} while (logLine != null);
		this.modelReady=true;
	}
	
	
	public String generateModel(){
		if (modelReady) {
			return this.model.getPrismModel();
		}
		else throw new IllegalStateException("The model is not ready.");
	}

	private State getSourceState(LogLine logLine) {
		if (logLine.getReferrer() != null
				&& mapper.matches(logLine.getReferrer()) != null) {
			log.debug("Exisisting user matched");
			return mapper.matches(logLine.getReferrer());
		} else if (tracker.getCurrentState(logLine.getIp()) != null) {
			log.debug("Exisisting user matched");
			return tracker.getCurrentState(logLine.getIp());
		}
		log.debug("No info about referrer");
		return this.model.getInitialState();
	}

	private boolean stem(LogLine logLine) {
		if (stemmer != null) {
			boolean stemmed = stemmer.matches(logLine);
			if (stemmed) {
				// log.debug("Line stemmed: "+logLine.getUrl());
				return true;
			}
		}
		return false;
	}

	public void registerStateExpression(String regEx, State state) {
		this.model.insertState(state);
		this.mapper.registerStateExpression(regEx, state);
	}

	public Stemmer getStemmer() {
		return stemmer;
	}

	public void setStemmer(Stemmer stemmer) {
		this.stemmer = stemmer;
	}
}
